using System;
using System.Collections.Generic;
using System.Text;
using Com.ContentRendering.Api.DataEngine;
using ContentRenderingApi.Configuration;
using ContentRenderingApi.Extender;
using ContentRenderingApi.Skeleton;
using ContentRenderingApi.Path;
using ContentRenderingApi.Data.Comparer;
using System.Linq;

namespace ContentRenderingApi.Data
{
    /// <summary/>
    internal abstract class AbstractData
    {
        private ReflectionCache _reflectionCache;
        private TransformationConfiguration _transformationConfiguration;
        private bool _isCaching;
        private AbstractData _parent;
        private Dictionary<string, List<AbstractData>> _cachedChildData;
        private string _cachedValue;

        /// <summary/>
        internal AbstractData(
            ReflectionCache reflectionCache,
            TransformationConfiguration transformationConfiguration,
            bool isCaching,
            AbstractData parent)
        {
            this._reflectionCache = reflectionCache;
            this._transformationConfiguration = transformationConfiguration;
            this._isCaching = isCaching;
            this._parent = parent;
            this._cachedChildData = new Dictionary<string, List<AbstractData>>();
        }

        /// <summary/>
        internal ReflectionCache ReflectionCache
        {
            get { return this._reflectionCache; }
        }

        /// <summary/>
        protected abstract void ResolveChildData(List<object> values, string name);

        /// <summary/>
        internal IEnumerator<AbstractData> FetchChildData(string name)
        {
            // if the cached value if set and caching is supported .. return it
            if (this._isCaching && this._cachedChildData.ContainsKey(name))
            {
                return this._cachedChildData[name].GetEnumerator();
            }

            List<object> values = new List<object>();

            NodeExtender nodeExtender = new NodeExtender(this);

            bool preOverride = false;

            // check for matching functions
            foreach (PreOverrideSkeleton preOverrideSkeleton in this._transformationConfiguration.PreOverrideSkeletons)
            {
                // does the path match ... if not move on
                if (!this.DoesChildPathMatch(name, preOverrideSkeleton.Path))
                {
                    continue;
                }
            
                // data was overridden
                preOverride = true;

                // invoke the function
                object[] outputsResults = preOverrideSkeleton.Invoke(nodeExtender);

                // move each element to the new list
                if (outputsResults != null)
                {
                    foreach (object outputsResult in outputsResults)
                    {
                        values.Add(outputsResult);
                    }
                }
            }

            // get child values only if data was not overridden
            if (!preOverride)
            {
                this.ResolveChildData(
                    values,
                    name);
            }

            // check for matching functions
            foreach (PostOverrideSkeleton postOverrideSkeleton in this._transformationConfiguration.PostOverrideSkeletons)
            {
                // does the path match ... if not move on
                if (!this.DoesChildPathMatch(name, postOverrideSkeleton.Path))
                {
                    continue;
                }

                // invoke the function
                postOverrideSkeleton.Invoke(nodeExtender, values);
            }

            // check for matching functions
            foreach (ConditionalFunctionSkeleton conditionalFunctionSkeleton in this._transformationConfiguration.ConditionalFunctionSkeletons)
            {
                // does the path match ... if not move on
                if (!this.DoesChildPathMatch(name, conditionalFunctionSkeleton.Path))
                {
                    continue;
                }

                // invoke the function
                bool conditionalResult = conditionalFunctionSkeleton.Invoke(nodeExtender);

                // move each element to the new list
                if (conditionalResult)
                {
                    values.Add(string.Empty);
                }
            }

            // check for matching functions
            foreach (FormattedOutputFunctionSkeleton outputFunctionSkeleton in this._transformationConfiguration.FormattedOutputFunctionSkeletons)
            {
                // does the path match ... if not move on
                if (!this.DoesChildPathMatch(name, outputFunctionSkeleton.Path))
                {
                    continue;
                }

                // invoke the function
                string outputResult = outputFunctionSkeleton.Invoke(nodeExtender);

                // move each element to the new list
                if (outputResult != null)
                {
                    values.Add(ValueReflection.Translate(outputResult));
                }
            }

            // check for matching functions
            foreach (FormattedOutputsFunctionSkeleton outputsFunctionSkeleton in this._transformationConfiguration.FormattedOutputsFunctionSkeletons)
            {
                // does the path match ... if not move on
                if (!this.DoesChildPathMatch(name, outputsFunctionSkeleton.Path))
                {
                    continue;
                }

                // invoke the function
                string[] outputsResults = outputsFunctionSkeleton.Invoke(nodeExtender);

                // move each element to the new list
                if (outputsResults != null)
                {
                    foreach (string outputsResult in outputsResults)
                    {
                        values.Add(ValueReflection.Translate(outputsResult));
                    }
                }
            }

            // check for matching functions
            foreach (ObserverFunctionSkeleton observerFunctionSkeleton in this._transformationConfiguration.ObserverFunctionSkeletons)
            {
                // does the name match ... if not move on
                if (!this.DoesChildPathMatch(name, observerFunctionSkeleton.Path))
                {
                    continue;
                } 

                // invoke the function
                observerFunctionSkeleton.Invoke(nodeExtender);
            }

            // check for matching functions
            foreach (WildcardOverrideSkeleton wildcardOverrideSkeleton in this._transformationConfiguration.WildcardOverrideSkeletons)
            {
                // does the path match ... if not move on
                if (!this.DoesSelfPathMatch(wildcardOverrideSkeleton.Path))
                {
                    continue;
                }

                // invoke the function
                wildcardOverrideSkeleton.Invoke(nodeExtender, name, values);
            }

            List<AbstractData> datas = new List<AbstractData>();

            foreach (object value in values)
            {
                datas.Add(new ChildData(
                    this._reflectionCache,
                    this._transformationConfiguration,
                    this._isCaching,
                    this,
                    name,
                    value,
                    values.Count));
            }

            List<SortComparer> sortComparers = new List<SortComparer>();

            // check for matching functions
            foreach (SortSkeleton sortSkeleton in this._transformationConfiguration.SortSkeletons)
            {
                // fetch the parents ...is this a match
                if (!this.DoesChildPathMatch(name, sortSkeleton.Path))
                {
                    continue;
                }

                // add the sort object
                sortComparers.Add(new SortComparer(sortSkeleton));
            }

            // perform the sort if any names were found
            if (sortComparers.Count != 0)
            {
                datas.Sort(AbstractData.ChainSortComparer(sortComparers));
            }

            List<SelfSortComparer> selfSortComparers = new List<SelfSortComparer>();

            // check for matching functions
            foreach (SelfSortSkeleton selfSortSkeleton in this._transformationConfiguration.SelfSortSkeletons)
            {
                // fetch the parents ...is this a match
                if (!this.DoesChildPathMatch(name, selfSortSkeleton.Path))
                {
                    continue;
                }

                // add the sort object
                selfSortComparers.Add(new SelfSortComparer(selfSortSkeleton));
            }

            // perform the sort if any names were found
            if (selfSortComparers.Count != 0)
            {
                datas.Sort(AbstractData.ChainSelfSortComparer(selfSortComparers));
            }

            List<ChildSortComparer> childSortComparers = new List<ChildSortComparer>();

            // check for matching functions
            foreach (ChildSortSkeleton childSortSkeleton in this._transformationConfiguration.ChildSortSkeletons)
            {
                // fetch the parents ...is this a m atch
                if (!this.DoesChildPathMatch(name, childSortSkeleton.Path))
                {
                    continue;
                }

                // add the sort object
                childSortComparers.Add(new ChildSortComparer(childSortSkeleton));
            }

            // perform the sort if any names were found
            if (childSortComparers.Count != 0)
            {
                datas.Sort(AbstractData.ChainChildSortComparer(childSortComparers));
            }

            // reset the positions after sorting ... read this comment the next time you
            // want to merge the position assignment with the constructor arguments
            for (int index =0; index < datas.Count; index++)
            {
                datas[index].AssignPosition(index + 1);
            }

            // if caching is supported ... then record it
            if (this._isCaching)
            {
                this._cachedChildData[name] = datas;
            }

            return datas.GetEnumerator();
        }

        /// <summary/>
        internal IEnumerator<AbstractData> FetchRawChildData(string name)
        {
            List<object> values = new List<object>();

            // get the base class to resolve the data data
            this.ResolveChildData(
                values,
                name);

            List<AbstractData> datas = new List<AbstractData>();

            foreach (object value in values)
            {
                datas.Add(new ChildData(
                    this._reflectionCache,
                    this._transformationConfiguration,
                    this._isCaching,
                    this,
                    name,
                    value,
                    values.Count));
            }

            // there is usually sorting ... but not here ... thats why assign position is used
            for (int index = 0; index < values.Count; index++)
            {
                datas[index].AssignPosition(index + 1);
            }

            return datas.GetEnumerator();
        }

        /// <summary/>
        private static ChildSortComparer ChainChildSortComparer(List<ChildSortComparer> childSortComparers)
        {
            // asssoaited the compar objects
            for (int index = 0; index < childSortComparers.Count - 1; index++)
            {
                childSortComparers[index].Successor = childSortComparers[index + 1];
            }

            // return the first in the chain
            return childSortComparers[0];
        }

        /// <summary/>
        private static SelfSortComparer ChainSelfSortComparer(List<SelfSortComparer> selfSortComparers)
        {
            // assosiate the compare objects
            for (int index = 0; index < selfSortComparers.Count - 1; index++)
            {
                selfSortComparers[index].Successor = selfSortComparers[index + 1];
            }

            // return the first in the chain
            return selfSortComparers[0];
        }

        /// <summary/>
        private static SortComparer ChainSortComparer(List<SortComparer> sortComparers)
        {
            // assosiate the compare objects
            for (int index = 0; index < sortComparers.Count - 1; index++)
            {
                sortComparers[index].Successor = sortComparers[index + 1];
            }

            // return the first in the chain
            return sortComparers[0];
        }

        /// <summary/>
        private bool DoesChildPathMatch(
            string childName,
            AbstractPath path)
        {
            // only compare the current name if the path is not absolute
            if (!path.IsFull())
            {
                return (path.PartialName == childName);
            }

            IEnumerable<string> reversedNames = path.FullNames.Reverse();

            using (IEnumerator<string> enumerator = reversedNames.GetEnumerator())
            {
                // there needs to be at least one record
                if (!enumerator.MoveNext())
                {
                    return false;
                }

                // compare the first items
                if (childName != enumerator.Current)
                {
                    return false;
                }

                AbstractData data = this;

                // keep looking until the data without a parent is found ... thats the root
                while (data._parent != null)
                {
                    // there needs to be at least one record
                    if (!enumerator.MoveNext())
                    {
                        return false;
                    }

                    // compare the first items
                    if (data.Name != enumerator.Current)
                    {
                        return false;
                    }

                    // fetch the parent data of the current data
                    data = data._parent;            
                }

                // fail if there are more records
                if (enumerator.MoveNext())
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary/>
        private bool DoesSelfPathMatch(AbstractPath path)
        {
            // only compare the current name if the path is not absolute
            if (!path.IsFull())
            {
                return (path.PartialName == this.Name);
            }

            IEnumerable<string> reversedNames = path.FullNames.Reverse();

            using (IEnumerator<string> enumerator = reversedNames.GetEnumerator())
            {
                AbstractData data = this;

                // keep looking until the data without a parent is found ... thats the root
                while (data._parent != null)
                {
                    // there needs to be at least one record
                    if (!enumerator.MoveNext())
                    {
                        return false;
                    }

                    // compare the first items
                    if (data.Name != enumerator.Current)
                    {
                        return false;
                    }

                    // fetch the parent data of the current data
                    data = data._parent;
                }

                // fail if there are more records
                if (enumerator.MoveNext())
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary/>
        internal IEnumerator<AbstractData> FetchRootData(string name)
        {
            // fetch the parent node 
            AbstractData data = this;

            // keep looking until the data without a parent is found ... thats the root
            while (data._parent != null)
            {
                // fetch the parent data of the current data
                data = data._parent;
            }

            // ask the data for its children
            return data.FetchChildData(name);
        }
        
        /// <summary/>
        internal IEnumerator<AbstractData> FetchParentData(int level)
        {
            // fetch the parent node (that is level 0)
            AbstractData data = this;

            // move back through the parents until the correct one is found
            for (int index = 0; index < level; index++)
            {
                // if the data isn't found then exit the loop
                if (data == null)
                {
                    break;
                }

                // fetch the parent data of the current data
                data = data._parent;
            }

            List<AbstractData> datas = new List<AbstractData>();

            // if the data is non-existent then return nothing
            if (data != null)
            {
                datas.Add(data);
            }

            // ask the data for its children
            return datas.GetEnumerator();
        }

        /// <summary/>
        internal string Value
        {
            get
            {
                // if the cached value if set and caching is supported .. return it
                if (this._isCaching && this._cachedValue != null)
                {
                    return this._cachedValue;
                }

                // fetch the raw value
                string value = this.FetchValue();

                // if caching is supported ... then record it
                if (this._isCaching)
                {
                    this._cachedValue = value;
                }

                // use the default formatter
                return value;
            }
        }

        /// <summary/>
        private string FetchValue()
        {
            // fetch the raw value
            object rawValue = this.RawValue;

            // if the value is null then exit
            if (rawValue == null)
            {
                return null;
            }

            // use the name formatters first
            foreach (LocalFormatterSkeleton localFormatterSkeleton in this._transformationConfiguration.LocalFormatterSkeletons)
            {
                // fetch the parents ...is this a match
                if (!this.DoesSelfPathMatch(localFormatterSkeleton.Path))
                {
                    continue;
                }

                // does the type match
                if (localFormatterSkeleton.Type.IsInstanceOfType(rawValue))
                {
                    return localFormatterSkeleton.Invoke(rawValue);
                }
            }

            // use the generic formatter second 
            foreach (GlobalFormatterSkeleton globalFormatterSkeleton in this._transformationConfiguration.GlobalFormatterSkeletons)
            {
                // does the type match
                if (globalFormatterSkeleton.Type.IsInstanceOfType(rawValue))
                {
                    return globalFormatterSkeleton.Invoke(rawValue);
                }
            }

            return ValueReflection.Translate(rawValue);
        }

        /// <summary/>
        protected abstract string Name { get; }

        /// <summary/>
        internal abstract object RawValue { get; }

        /// <summary/>
        internal abstract int Length { get; }

        /// <summary/>
        internal abstract int Position { get; }

        /// <summary/>
        internal AbstractData Parent
        {
            get { return this._parent; }
        }

        /// <summary/>
        protected abstract void AssignPosition(int position);
    }
}
